package lzupdater.timing;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

//LZRosterUpdater, a java client to update LZRoster
//Copyright (C) 2008 Fraskor from Krasus (e-mail: fraskor@gmail.com)
//
//This file is part of LZRosterUpdater.
//
//LZRosterUpdater is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this program.  If not, see <http://www.gnu.org/licenses/>.

/**
 * 
 * @author Fraskor
 */
public class CentralTimer extends Thread {

	private final Object lock = new Object();

	private static final CentralTimer singleton = new CentralTimer();

	/**
	 * Add a timed task to the central. The central will take care of firing the
	 * event a the right moment
	 * 
	 * @param task
	 *            the task to perform
	 * @param delai
	 *            the delai after which the task is performed (in ms). The zero
	 *            time is the time when the task is added by this function
	 * @param mode
	 *            the mode of the task (played only once or repeated)
	 */
	public static void addTimedTask(TimedTask task, long delai, TimerMode mode) {
		if (singleton.findTimer(task) != null) {
			throw new RuntimeException(
					"Task is already register in the CentralTimer");
		}
		singleton.addTimer(new Timer(task, delai, mode));
	}

	/**
	 * Request the termination of the timed task in parametre
	 * 
	 * @param task :
	 *            the task to stop
	 */
	public static void stopTimedTask(TimedTask task) {
		Timer t = singleton.findTimer(task);
		if (t != null) {
			t.stop();
		}
	}

	/**
	 * Reset the timed task
	 * 
	 * @param task
	 *            the task to reset
	 * @param delaiEnMs
	 *            the new delai (time reference is the time of the to the reset
	 *            method)
	 * @param mode
	 *            the new mode for this task
	 */
	public static void resetTimedTask(TimedTask task, double delaiEnMs,
			TimerMode mode) {
		Timer t = singleton.findTimer(task);
		if (t != null) {
			t.reset(delaiEnMs, mode);
		} else {
			singleton.addTimer(new Timer(task, delaiEnMs, mode));
		}
	}

	/**
	 * Search the registered tasks for a the specified task
	 * 
	 * @param task
	 *            the searched task
	 * @return a reference to the task or null
	 */
	private Timer findTimer(TimedTask task) {
		synchronized (lock) {
			for (Timer t : singleton.timerToPlay) {
				if (t.getTask() == task) {
					return t;
				}
			}
		}
		return null;
	}

	private CentralTimer() {
		start();
	}

	private long nextTimerTime;

	private List<Timer> timerToPlay = Collections
			.synchronizedList(new LinkedList<Timer>());

	private void addTimer(Timer t) {
		synchronized (singleton.lock) {
			timerToPlay.add(t);
			singleton.lock.notify();
		}

	}

	@Override
	public void run() {
		while (true) {

			while (timerToPlay.isEmpty()) {
				synchronized (lock) {
					try {
						lock.wait();
					} catch (InterruptedException e) {
					}
				}
			}

			synchronized (lock) {
				ListIterator<Timer> it = timerToPlay.listIterator();

				boolean firstPass = true;
				while (it.hasNext()) {
					Timer t = it.next();

					t.checkAndLaunch(System.nanoTime());
					if (t.isDone()) {
						it.remove();
					} else if (firstPass || t.getFiringTime() < nextTimerTime) {
						firstPass = false;
						nextTimerTime = t.getFiringTime();
					}
				}
			}

			long delai = nextTimerTime - System.nanoTime();

			if (delai > 10000) {
				long msec = delai / 1000000;
				int nsec = (int) (delai % 1000000);
				try {
					synchronized (lock) {
						lock.wait(msec, nsec);
					}
				} catch (InterruptedException e) {
				}
			}
		}
	}

	/**
	 * Internal Class that save the task to perfom and its mode and time when to
	 * perfom it
	 * 
	 * @author Administrateur
	 * 
	 */
	private static class Timer {

		private boolean stopped;
		private long delai;
		private long firingTime;
		private TimerMode mode;
		private TimedTask task;

		public Timer(TimedTask task, double delai, TimerMode mode) {
			this.task = task;
			reset(delai, mode);
		}

		/**
		 * Check if the task shoudl be perfomerd according to the time, mode and
		 * request to stop (if any)
		 * 
		 * @param time
		 * @return true if the task has been performed, else false
		 */
		public boolean checkAndLaunch(long time) {
			if (!stopped && time >= firingTime && !mode.doNotPlayAgain()) {
				task.performTask(time);
				firingTime = time + delai;
				mode.setTimedTaskPlayed();
				if (mode.doNotPlayAgain()) {
					this.stop();
				}
				return true;
			}
			return false;
		}

		public boolean isDone() {
			return stopped;
		}

		public void reset(double delai, TimerMode mode) {
			this.delai = (long) (1e6 * delai);
			firingTime = System.nanoTime() + this.delai;
			this.mode = mode;
			stopped = false;
		}

		private void stop() {
			stopped = true;
			task.taskDone();
		}

		public long getFiringTime() {
			return firingTime;
		}

		public TimerMode getMode() {
			return mode;
		}

		public TimedTask getTask() {
			return task;
		}

		public long getDelai() {
			return delai;
		}

		@Override
		public boolean equals(Object obj) {
			return (obj instanceof Timer) && task == ((Timer) obj).task;
		}

		@Override
		public int hashCode() {
			return task.hashCode();
		}

		@Override
		public String toString() {
			return String.format("%s  %f", task.getClass().getSimpleName(),
					delai * 1e-6);
		}

	}

}
